########################################
##
## This pluging returns the access level
## for a user.

import xml.dom.minidom
import irc_protocol
import hashlib
import datetime
import time
import settings

## TODO : Add this to a config file.
salt="PeJtM6LnZluNko7cIvFku6LWwnNiqeJCRRCOjPCXISt4BPB5BBg560IUaHpYwnr3k6YjP4ft2fpf9XrwYNn9bJkG9r1OuNSFHTRSBJTsOixAYcZRBXnX8qWSDLpEon2OTVby4KNGJGBY3YWm50gJVBqyN7QBs35oPbwyjdeV28SlpEBVS9AHjt4YgbMNoI6Z4spkfGfQmVI1JyyGP0yZ9zo0fnCj6kHAMIkZvH7l0QpWN7g7ZutFaHEx1cIFD1SH"

########################################
##
## list of all users and of
## authenticated users
## 
users = {}
authenticated_users = {}


########################################
##
## Define a user of the bot
## 
class User(object):
    def __init__(self, username_or_node, user_password = ""):
        if isinstance(username_or_node, xml.dom.minidom.Element):
            self.__username = username_or_node.getAttribute("username")
            self.__password = username_or_node.getAttribute("password")
            if username_or_node.getAttribute("last_login") <> "":
                self.__last_login = datetime.datetime(*time.strptime(username_or_node.getAttribute("last_login"), "%Y-%m-%d %H:%M")[0:5])
            else:
                self.__last_login = None
            self.__user_level = int(username_or_node.getAttribute("user_level"))

        else:
            self.__username = username_or_node
            self.__password = ""
            self.__last_login = None
            self.__user_level = 1

            print "before __set_password"
            self.__set_password(user_password)
            print "after __set_password"

        self.__authentication_token = None

    # Read only property for the username
    def __get_username(self):
        return self.__username

    username = property(__get_username, None)

    # Read/write property for the las_login
    def __get_last_login(self):
        return self.__last_login

    def __set_last_login(self, value):
        self.__last_login = value

    last_login = property(__get_last_login, __set_last_login)

    # Read/write property for user_level
    def __get_user_level(self):
        return self.__user_level

    def __set_user_level(self, value):
        self.__user_level = value

    user_level = property(__get_user_level, __set_user_level)

    # Write only property for password and a check_password function
    def __set_password(self, value):
        self.__password = hash_password(value)

    password = property(None, __set_password)

    # Read/wrote property for the authentication_token
    def __set_authentication_token(self, value):
        self.__authentication_token = value

    def __get_authentication_token(self):
        return self.__authentication_token

    authentication_token = property(__set_authentication_token, __get_authentication_token)

    def check_password(self, new_password):
        return self.__password == hash_password(new_password)

    # add attributes to a xml node to save the user
    def add_attributes_xml(self, node):
        node.setAttribute("username", self.__username)
        node.setAttribute("password", self.__password)
        if self.__last_login is not None:
            node.setAttribute("last_login", self.__last_login.strftime("%Y-%m-%d %H:%M"))
        
        node.setAttribute("user_level", str(self.__user_level))

########################################
##
## Create a user
##
def process_command_create(server, command, command_line):
    if len(command_line) == 4:

        # We check if this user already exist
        if command_line[2] not in users:
            new_user = User(command_line[2], command_line[3])
            users[new_user.username] = new_user
            save_users()

            server.send(
                irc_protocol.reply_private(command,
                    "User %s sucessfuly created." % (new_user.username)))
        else:
            server.send(irc_protocol.reply_private(command, "A user with this username already exist."))      
    else:
        server.send(irc_protocol.reply_private(command, 'Invalid parameters. Use "!help users create" to get more help.'))

########################################
##
## send a list of users
##    
def process_command_list(server, command, command_line):
    server.send(irc_protocol.reply_private(command, "Here is a list of users:"))
    server.send(irc_protocol.reply_private(command, "-------------------------------------------------"))
    server.send(irc_protocol.reply_private(command, "username                  level  last login"))
    server.send(irc_protocol.reply_private(command, "-------------------------------------------------"))
            
    for username in users:
        a_user = users[username]
        if a_user.last_login is not None:
            last_login = a_user.last_login.strftime("%Y-%m-%d %H:%M")
        else:
            last_login = "None"
        server.send(irc_protocol.reply_private(command,"%-25s %-5s  %s" % (username, a_user.user_level, last_login)))

    server.send( irc_protocol.reply_private(command, "-------------------------------------------------"))

########################################
##
## Allow a user to login.
## 
def process_command_login(server, command, command_line):
    if len(command_line) == 4:
        if command_line[2] in users and users[command_line[2]].check_password(command_line[3]):
            authenticated_users[command[0]] = users[command_line[2]]
            authenticated_users[command[0]].last_login = datetime.datetime.now()
            save_users()
            server.send(irc_protocol.reply_private(command, "Sucessfully authenticated as %s." % (command_line[2])))
        else:
            server.send(irc_protocol.reply_private(command, "User name or password invalid. Please try again."))
        
    else:
        server.send(irc_protocol.reply_private(command, 'Invalid numbers of parameters. Use "!help users" to get more help.'))

########################################
##
## Allow a user to logout.
## 
def process_command_logout(server, command, command_line):
    if command[0] in authenticated_users:
        del authenticated_users[command[0]]
    else:
        server.send(irc_protocol.reply_private(command, "You were not authenticated. There is no need to log out."))

########################################
##
## Modify the user_level of a user
##
def process_command_level(server, command, command_line):
    if len(command_line) == 4:
        if command_line[2] in users:
            try:
                level = int(command_line[3])
            except ValueError:
                server.send(irc_protocol.reply_private(command, "Invalid parameter (must be an integer between 1 and 99): %s" % (command_line[3])))
                return

            if 1 <= level <= 99:
                users[command_line[2]].user_level = level
                save_users()
                server.send(irc_protocol.reply_private(command, "Level of %s sucessfully changed to %s" % (command_line[2], command_line[3])))
            else:
                server.send(irc_protocol.reply_private(command, "Invalid parameter (must be an integer between 1 and 99): %s" % (command_line[3])))
        else:
            server.send(irc_protocol.reply_private(command, "The user %s does not exist" % (command_line[2])))
    else:
        server.send(irc_protocol.reply_private(command, 'Invalid number of parameters. Use "!help users" to get more help.'))

########################################
##
## Modify the password of an 
## authenticated user
##
def process_command_password(server, command, command_line):
    if len(command_line) == 4:
        # is the user authenticated
        if command[0] in authenticated_users:
            if authenticated_users[command[0]].check_password(command_line[2]):
                print authenticated_users[command[0]]
                authenticated_users[command[0]].password = command_line[3]
                save_users()
                server.send(irc_protocol.reply_private(command, "Your password has been sucessfully changed."))
                print "Check authenticated_users " +  ('True','False')[not authenticated_users[command[0]].check_password(command_line[3])]
                print "Check users " + ('True','False')[not users[authenticated_users[command[0]].username].check_password(command_line[3])]
            else:
                server.send(irc_protocol.reply_private(command, 'Invalid password. Use "!help users" to get more help.'))
        else:
            server.send(irc_protocol.reply_private(command, 'You need to be authenticated to change your password. Use the "!users login" command to authenticate yourself.'))
    else:
        server.send(irc_protocol.reply_private(command, 'Invalid number of parameters. Use "!help users" to get more help.'))

########################################
##
## Remove a user from the user list
##
def process_command_remove(server, command, command_line):
    if len(command_line) == 3:
        if command_line[2] in users:
            del users[command_line[2]]
            save_users()
            server.send(irc_protocol.reply_private(command, "User %s was sucessfuly removed." % (command_line[2])))
        else:
            server.send(irc_protocol.reply_private(command, "The user %s does not exist." % (command_line[2])))

########################################
##
## process the !users commands
## 
def process_command(server, command, help, authenticate):
    commands = {"create": process_command_create,
                "list": process_command_list,
                "login": process_command_login,
                "logout": process_command_logout,
                "level": process_command_level,
                "password": process_command_password,
                "remove": process_command_remove}

    chanel_prefixes = "&#+!%"

    if command[2][0] not in chanel_prefixes:
        command_line = command[3].split()
                
        if len(command_line) >= 2 and command_line[1] in commands:
            commands[command_line[1]](server, command, command_line)
        else:
            server.send(irc_protocol.reply_private(command, 'Invalid command. Use "!help users" to get more help.'))

    else:
        server.send(irc_protocol.reply_private(command, "The !users command must be sent in private."))

########################################
##
## Look for users leaving the chanel
## 
def part_tracker (server, command):
    if (command[1] == "PART" or command[1] == "QUIT") and command[0] in authenticated_users:
        del authenticated_users[command[0]]

########################################
##
## Save the user list.
## 
def save_users():
    doc = xml.dom.minidom.Document()
    users_node = doc.createElement("users")
    doc.appendChild(users_node)

    for username in users:
        user_node = doc.createElement("user")
        users[username].add_attributes_xml(user_node)
        users_node.appendChild(user_node)

    doc.writexml(open("users.xml", "w"), "", " ", "\n", "utf-8")
    
########################################
##
## Load the users list
## 
def load_users():
    doc = xml.dom.minidom.parse("users.xml")
    for node in doc.childNodes:
        if node.tagName == "users":
            for user_node in node.childNodes:
                if isinstance(user_node, xml.dom.minidom.Element) and user_node.tagName == "user":
                    a_user = User(user_node)
                    users[a_user.username] = a_user

########################################
##
## Hash the password passed as parameter
##
def hash_password(passwd):
    m = hashlib.sha256()
    m.update(salt)
    m.update(passwd)
    return m.hexdigest()

########################################
##
## Link this module to the bot
## 
def link(commands, help, params):
    params["users"] = {"pi": settings.Param("pi", "3.14", lambda x: float(x) >= 0)}

    help["users"] = ["manage users and login.",
                     ["All these command must be sent in private:",
                      " ",
                      "To create a user, send:",
                      "   !users create <username> <password>",
                      "To remove a user, send:",
                      "   !users remove <username>",
                      "To authenticate yourself with the bot, send: ",
                      "   !users login <username> <password>",
                      "To cancel your authentification, send:",
                      "   !users logout",
                      "To change your password, send:",
                      "   !users password <old_password> <new_password>",
                      "To see a list of all users, send:",
                      "   !users list",
                      "To modify the level of a user, send:",
                      "   !users level <username> <new_level>"]
                    ]

    commands["users"] = process_command

########################################
##
## Link this module to the bot
## 
def link_tracker(trackers):
    trackers.append(part_tracker)    

########################################
##
## Return the user_level from
## a hostmask.
## 
def user_level( user ):
    if user in authenticated_users:
        return authenticated_users[user].user_level
    else:
        return 0

# When the module is imported we load the user list.
load_users()
